package com.yinhai.ta404.component.security.base.access;

import com.yinhai.ta404.component.security.base.autoconfigure.WebSecurityConfig;
import com.yinhai.ta404.component.security.base.user.TaUserDetails;
import com.yinhai.ta404.core.IConstants;
import com.yinhai.ta404.core.context.ITaContext;
import com.yinhai.ta404.core.event.EventPublish;
import com.yinhai.ta404.core.security.ISecurityConstants;
import com.yinhai.ta404.core.security.matcher.UrlPathMatcher;
import com.yinhai.ta404.core.security.service.ITaResourceReadService;
import com.yinhai.ta404.core.security.service.ITaUserReadService;
import com.yinhai.ta404.core.security.vo.BaseResourceVo;
import com.yinhai.ta404.core.security.vo.RestUrlVo;
import com.yinhai.ta404.core.utils.StringUtils;
import com.yinhai.ta404.core.utils.ValidateUtil;
import com.yinhai.ta404.core.utils.WebUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.Timestamp;
import java.util.*;

import static com.yinhai.ta404.core.IConstants.*;


public class TaAccessDecisionManager extends AffirmativeBased implements AccessDecisionManager {

    @Autowired
    IMenuStore menuStore;
    @Autowired
    ITaContext taContext;
    @Autowired
    private WebSecurityConfig webSecurityConfig;
    @Autowired
    private ITaUserReadService taUserReadService;
    @Autowired
    private ITaResourceReadService taResourceReadService;
    @Autowired
    private EventPublish eventPublish;

    public TaAccessDecisionManager(List<AccessDecisionVoter<?>> decisionVoters) {
        super(decisionVoters);
    }

    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        HttpServletRequest request = ((FilterInvocation) object).getRequest();
        String requestUrl = ((FilterInvocation) object).getRequestUrl();


        if (requestUrl.endsWith(".html")) {
            return;
        }

        Object principal = authentication.getPrincipal();

        String userid = null;
        if (principal instanceof TaUserDetails) {
            TaUserDetails user = (TaUserDetails) principal;


            taContext.setTaUser(user.getUser());


            userid = user.getUser().getUserId();
            if (taUserReadService.isDeveloper(userid)) {
                return;
            }
        }
        if (requestUrl.contains(";jsessionid")) {

            requestUrl = requestUrl.substring(0, requestUrl.indexOf(";jsessionid"));
        }
        if (requestUrl.contains("?")) {
            requestUrl = requestUrl.substring(0, requestUrl.indexOf('?'));
        }




        Set<String> permitUrls = webSecurityConfig.getPermitUrls();
        if (!ValidateUtil.isEmpty(permitUrls)) {
            for (String nextVal : permitUrls) {
                if (requestUrl.equals(nextVal) || UrlPathMatcher.pathMatchesUrl(nextVal, requestUrl)) {
                    return;
                }
            }
        }
        List<BaseResourceVo> queryResourceNoLogin = menuStore.getResourceNoLogin(authentication, object);
        if (queryResourceNoLogin == null) {
            queryResourceNoLogin = taResourceReadService.queryResourceNoLogin();
        }
        String param = (((FilterInvocation) object).getRequest()).getParameter("_modulePartId_");
        String resourceId = null == param ? StringUtils.EMPTY : param;
        for (BaseResourceVo baseResourceVo : queryResourceNoLogin) {
            if (baseResourceVo.getResourceId().equals(resourceId)) {
                return;
            }
        }


        Set<String> loginPermitUrls = webSecurityConfig.getLoginPermitUrls();
        if (!"anonymousUser".equals(principal) && !ValidateUtil.isEmpty(loginPermitUrls)) {
            for (String nextVal : loginPermitUrls) {
                if (requestUrl.equals(nextVal) || UrlPathMatcher.pathMatchesUrl(nextVal, requestUrl)) {
                    return;
                }
            }
        }
        List<BaseResourceVo> queryResourceLogin = menuStore.getResourceLogin(authentication, object);
        if (queryResourceLogin == null) {
            queryResourceLogin = taResourceReadService.queryResourceLogin();
        }
        for (BaseResourceVo baseResourceVo : queryResourceLogin) {
            if (!"anonymousUser".equals(principal) && baseResourceVo.getResourceId().equals(resourceId)) {
                return;
            }
        }

        requestUrl = requestUrl.substring(1);

        List<RestUrlVo> btnRestUrlList = menuStore.getBtnResource(authentication, object, resourceId);

        List<String> btnAuthUrl = new ArrayList<>();

        List<String> btnNoAuthUrl = menuStore.getBtnNoAuthUrl(authentication, object, resourceId);
        for (RestUrlVo restUrlVo : btnRestUrlList) {

            if (ISecurityConstants.INHERIT_AUTHORITY_POLICY.equals(restUrlVo.getAuthorityPolicy())) {
                btnAuthUrl.add(restUrlVo.getRestUrl());
            }

            if (ISecurityConstants.SELF_AUTHORITY_POLICY.equals(restUrlVo.getAuthorityPolicy()) && YES.equals(restUrlVo.getChecked())) {
                btnAuthUrl.add(restUrlVo.getRestUrl());
            }

            if (ISecurityConstants.SELF_AUTHORITY_POLICY.equals(restUrlVo.getAuthorityPolicy()) && NO.equals(restUrlVo.getChecked())) {
                btnNoAuthUrl.add(restUrlVo.getRestUrl());
            }
        }

        for (String url : btnNoAuthUrl) {
            if (requestUrl.equals(url)) {
                publishAccessDenyEvent(userid, resourceId, request);
                throw new AccessDeniedException("对不起，您没有访问该资源的权限！ ");
            }
        }
        for (String url : btnAuthUrl) {
            if (requestUrl.equals(url) || UrlPathMatcher.pathMatchesUrl(url, requestUrl)) {
                return;
            }
        }

        List<RestUrlVo> restUrlList = menuStore.getResourceAuthInfo(authentication, object, resourceId);
        if (ValidateUtil.isNotEmpty(restUrlList)) {

            List<String> authorityUrls = new ArrayList<>();

            List<String> noAuthorityUrls = new ArrayList<>();
            for (RestUrlVo restUrlVo : restUrlList) {

                if (ISecurityConstants.INHERIT_AUTHORITY_POLICY.equals(restUrlVo.getAuthorityPolicy())) {
                    authorityUrls.add(restUrlVo.getRestUrl());
                }

                if (ISecurityConstants.SELF_AUTHORITY_POLICY.equals(restUrlVo.getAuthorityPolicy()) && YES.equals(restUrlVo.getChecked())) {
                    authorityUrls.add(restUrlVo.getRestUrl());
                }

                if (ISecurityConstants.SELF_AUTHORITY_POLICY.equals(restUrlVo.getAuthorityPolicy()) && NO.equals(restUrlVo.getChecked())) {
                    noAuthorityUrls.add(restUrlVo.getRestUrl());
                }
            }


            for (String url : noAuthorityUrls) {
                if (requestUrl.equals(url)) {
                    publishAccessDenyEvent(userid, resourceId, request);
                    throw new AccessDeniedException("对不起，您没有访问该资源的权限！ ");
                }
            }
            for (String url : authorityUrls) {
                if (requestUrl.equals(url) || UrlPathMatcher.pathMatchesUrl(url, requestUrl)) {
                    return;
                }
            }
        }
        publishAccessDenyEvent(userid, resourceId, request);
        throw new AccessDeniedException("对不起，您没有访问该资源的权限！ ");
    }

    private void publishAccessDenyEvent(String userId, String resourceId, HttpServletRequest request) {

        Map<String, Object> eventDto = new HashMap<>(16);
        try {
            eventDto.put(EVENT_PARAM_SERVERIP, InetAddress.getLocalHost().getHostAddress());
        } catch (UnknownHostException e) {
            logger.error("serverip Acquisition failure");
        }
        eventDto.put(EVENT_PARAM_USERID, userId);
        eventDto.put(EVENT_PARAM_RESOURCEID, resourceId);
        eventDto.put(EVENT_PARAM_ACCESSDENYTYPE, ACCESS_DENY_NOAUTH_02);
        eventDto.put(EVENT_PARAM_ACCESSTIME, new Timestamp(request.getSession().getLastAccessedTime()));
        eventDto.put(EVENT_PARAM_URL, String.valueOf(request.getRequestURL()));
        eventDto.put(EVENT_PARAM_CLIENTIP, WebUtil.getClientIp(request));
        eventDto.put(EVENT_PARAM_CLIENTSYSTEM, request.getParameter(REQUEST_PARAM_CLIENTSYSTEM));
        eventDto.put(EVENT_PARAM_CLIENTBROWSER, request.getParameter(REQUEST_PARAM_CLIENTBROWSER));
        eventDto.put(EVENT_PARAM_CLIENTSCREENSIZE, request.getParameter(REQUESTPARAM_CLIENTSCREENSIZE));
        eventPublish.publish(eventDto, IConstants.ACCESS_FAIL_EVENT_ID);
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }

}
